#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <ctype.h>
#include "ILexer.h"
#include "Scintilla.h"
#include "SciLexer.h"
#include "WordList.h"
#include "LexAccessor.h"
#include "Accessor.h"
#include "StyleContext.h"
#include "CharacterSet.h"
#include "LexerModule.h"

#ifdef SCI_NAMESPACE
using namespace Scintilla;
#endif




static int LongDelimCheck( StyleContext &sc ) {
  int sep = 1;
  while( sc.GetRelative( sep ) == '=' && sep < 0xFF ) {
    sep++;
  }
  if( sc.GetRelative( sep ) == sc.ch ) {
    return sep;
  }
  return 0;
}

static void ColouriseLuaDoc(
  unsigned int startPos,
  int length,
  int initStyle,
  WordList *keywordlists[],
  Accessor &styler ) {
  WordList &keywords = *keywordlists[0];
  WordList &keywords2 = *keywordlists[1];
  WordList &keywords3 = *keywordlists[2];
  WordList &keywords4 = *keywordlists[3];
  WordList &keywords5 = *keywordlists[4];
  WordList &keywords6 = *keywordlists[5];
  WordList &keywords7 = *keywordlists[6];
  WordList &keywords8 = *keywordlists[7];
  CharacterSet setWordStart( CharacterSet::setAlpha, "_", 0x80, true );
  CharacterSet setWord( CharacterSet::setAlphaNum, "_", 0x80, true );
  CharacterSet setNumber( CharacterSet::setDigits, ".-+abcdefpABCDEFP" );
  CharacterSet setExponent( CharacterSet::setNone, "eEpP" );
  CharacterSet setLuaOperator( CharacterSet::setNone, "*/-+()={}~[];<>,.^%:#" );
  CharacterSet setEscapeSkip( CharacterSet::setNone, "\"'\\" );
  int currentLine = styler.GetLine( startPos );
  int nestLevel = 0;
  int sepCount = 0;
  int stringWs = 0;
  if( initStyle == SCE_LUA_LITERALSTRING || initStyle == SCE_LUA_COMMENT ||
      initStyle == SCE_LUA_STRING || initStyle == SCE_LUA_CHARACTER ) {
    int lineState = styler.GetLineState( currentLine - 1 );
    nestLevel = lineState >> 9;
    sepCount = lineState & 0xFF;
    stringWs = lineState & 0x100;
  }
  if( initStyle == SCE_LUA_STRINGEOL || initStyle == SCE_LUA_COMMENTLINE || initStyle == SCE_LUA_PREPROCESSOR ) {
    initStyle = SCE_LUA_DEFAULT;
  }
  StyleContext sc( startPos, length, initStyle, styler );
  if( startPos == 0 && sc.ch == '#' ) {
    sc.SetState( SCE_LUA_COMMENTLINE );
  }
  for( ; sc.More(); sc.Forward() ) {
    if( sc.atLineEnd ) {
      currentLine = styler.GetLine( sc.currentPos );
      switch( sc.state ) {
        case SCE_LUA_LITERALSTRING:
        case SCE_LUA_COMMENT:
        case SCE_LUA_STRING:
        case SCE_LUA_CHARACTER:
          styler.SetLineState( currentLine, ( nestLevel << 9 ) | stringWs | sepCount );
          break;
        default:
          styler.SetLineState( currentLine, 0 );
          break;
      }
    }
    if( sc.atLineStart && ( sc.state == SCE_LUA_STRING ) ) {
      sc.SetState( SCE_LUA_STRING );
    }
    if( ( sc.state == SCE_LUA_STRING || sc.state == SCE_LUA_CHARACTER ) &&
        sc.ch == '\\' ) {
      if( sc.chNext == '\n' || sc.chNext == '\r' ) {
        sc.Forward();
        if( sc.ch == '\r' && sc.chNext == '\n' ) {
          sc.Forward();
        }
        continue;
      }
    }
    if( sc.state == SCE_LUA_OPERATOR ) {
      if( sc.ch == ':' && sc.chPrev == ':' ) {
        sc.Forward();
        int ln = 0;
        while( IsASpaceOrTab( sc.GetRelative( ln ) ) ) {
          ln++;
        }
        int ws1 = ln;
        if( setWordStart.Contains( sc.GetRelative( ln ) ) ) {
          int c, i = 0;
          char s[100];
          while( setWord.Contains( c = sc.GetRelative( ln ) ) ) {
            if( i < 90 )
            { s[i++] = static_cast<char>( c ); }
            ln++;
          }
          s[i] = '\0';
          int lbl = ln;
          if( !keywords.InList( s ) ) {
            while( IsASpaceOrTab( sc.GetRelative( ln ) ) )
            { ln++; }
            int ws2 = ln - lbl;
            if( sc.GetRelative( ln ) == ':' && sc.GetRelative( ln + 1 ) == ':' ) {
              sc.ChangeState( SCE_LUA_LABEL );
              if( ws1 ) {
                sc.SetState( SCE_LUA_DEFAULT );
                sc.ForwardBytes( ws1 );
              }
              sc.SetState( SCE_LUA_LABEL );
              sc.ForwardBytes( lbl - ws1 );
              if( ws2 ) {
                sc.SetState( SCE_LUA_DEFAULT );
                sc.ForwardBytes( ws2 );
              }
              sc.SetState( SCE_LUA_LABEL );
              sc.ForwardBytes( 2 );
            }
          }
        }
      }
      sc.SetState( SCE_LUA_DEFAULT );
    } else if( sc.state == SCE_LUA_NUMBER ) {
      if( !setNumber.Contains( sc.ch ) ) {
        sc.SetState( SCE_LUA_DEFAULT );
      } else if( sc.ch == '-' || sc.ch == '+' ) {
        if( !setExponent.Contains( sc.chPrev ) ) {
          sc.SetState( SCE_LUA_DEFAULT );
        }
      }
    } else if( sc.state == SCE_LUA_IDENTIFIER ) {
      if( !( setWord.Contains( sc.ch ) || sc.ch == '.' ) || sc.Match( '.', '.' ) ) {
        char s[100];
        sc.GetCurrent( s, sizeof( s ) );
        if( keywords.InList( s ) ) {
          sc.ChangeState( SCE_LUA_WORD );
          if( strcmp( s, "goto" ) == 0 ) {
            sc.SetState( SCE_LUA_DEFAULT );
            while( IsASpaceOrTab( sc.ch ) && !sc.atLineEnd )
            { sc.Forward(); }
            if( setWordStart.Contains( sc.ch ) ) {
              sc.SetState( SCE_LUA_LABEL );
              sc.Forward();
              while( setWord.Contains( sc.ch ) )
              { sc.Forward(); }
              sc.GetCurrent( s, sizeof( s ) );
              if( keywords.InList( s ) )
              { sc.ChangeState( SCE_LUA_WORD ); }
            }
            sc.SetState( SCE_LUA_DEFAULT );
          }
        } else if( keywords2.InList( s ) ) {
          sc.ChangeState( SCE_LUA_WORD2 );
        } else if( keywords3.InList( s ) ) {
          sc.ChangeState( SCE_LUA_WORD3 );
        } else if( keywords4.InList( s ) ) {
          sc.ChangeState( SCE_LUA_WORD4 );
        } else if( keywords5.InList( s ) ) {
          sc.ChangeState( SCE_LUA_WORD5 );
        } else if( keywords6.InList( s ) ) {
          sc.ChangeState( SCE_LUA_WORD6 );
        } else if( keywords7.InList( s ) ) {
          sc.ChangeState( SCE_LUA_WORD7 );
        } else if( keywords8.InList( s ) ) {
          sc.ChangeState( SCE_LUA_WORD8 );
        }
        sc.SetState( SCE_LUA_DEFAULT );
      }
    } else if( sc.state == SCE_LUA_COMMENTLINE || sc.state == SCE_LUA_PREPROCESSOR ) {
      if( sc.atLineEnd ) {
        sc.ForwardSetState( SCE_LUA_DEFAULT );
      }
    } else if( sc.state == SCE_LUA_STRING ) {
      if( stringWs ) {
        if( !IsASpace( sc.ch ) ) {
          stringWs = 0;
        }
      }
      if( sc.ch == '\\' ) {
        if( setEscapeSkip.Contains( sc.chNext ) ) {
          sc.Forward();
        } else if( sc.chNext == 'z' ) {
          sc.Forward();
          stringWs = 0x100;
        }
      } else if( sc.ch == '\"' ) {
        sc.ForwardSetState( SCE_LUA_DEFAULT );
      } else if( stringWs == 0 && sc.atLineEnd ) {
        sc.ChangeState( SCE_LUA_STRINGEOL );
        sc.ForwardSetState( SCE_LUA_DEFAULT );
      }
    } else if( sc.state == SCE_LUA_CHARACTER ) {
      if( stringWs ) {
        if( !IsASpace( sc.ch ) ) {
          stringWs = 0;
        }
      }
      if( sc.ch == '\\' ) {
        if( setEscapeSkip.Contains( sc.chNext ) ) {
          sc.Forward();
        } else if( sc.chNext == 'z' ) {
          sc.Forward();
          stringWs = 0x100;
        }
      } else if( sc.ch == '\'' ) {
        sc.ForwardSetState( SCE_LUA_DEFAULT );
      } else if( stringWs == 0 && sc.atLineEnd ) {
        sc.ChangeState( SCE_LUA_STRINGEOL );
        sc.ForwardSetState( SCE_LUA_DEFAULT );
      }
    } else if( sc.state == SCE_LUA_LITERALSTRING || sc.state == SCE_LUA_COMMENT ) {
      if( sc.ch == '[' ) {
        int sep = LongDelimCheck( sc );
        if( sep == 1 && sepCount == 1 ) {
          nestLevel++;
          sc.Forward();
        }
      } else if( sc.ch == ']' ) {
        int sep = LongDelimCheck( sc );
        if( sep == 1 && sepCount == 1 ) {
          nestLevel--;
          sc.Forward();
          if( nestLevel == 0 ) {
            sc.ForwardSetState( SCE_LUA_DEFAULT );
          }
        } else if( sep > 1 && sep == sepCount ) {
          sc.Forward( sep );
          sc.ForwardSetState( SCE_LUA_DEFAULT );
        }
      }
    }
    if( sc.state == SCE_LUA_DEFAULT ) {
      if( IsADigit( sc.ch ) || ( sc.ch == '.' && IsADigit( sc.chNext ) ) ) {
        sc.SetState( SCE_LUA_NUMBER );
        if( sc.ch == '0' && toupper( sc.chNext ) == 'X' ) {
          sc.Forward();
        }
      } else if( setWordStart.Contains( sc.ch ) ) {
        sc.SetState( SCE_LUA_IDENTIFIER );
      } else if( sc.ch == '\"' ) {
        sc.SetState( SCE_LUA_STRING );
        stringWs = 0;
      } else if( sc.ch == '\'' ) {
        sc.SetState( SCE_LUA_CHARACTER );
        stringWs = 0;
      } else if( sc.ch == '[' ) {
        sepCount = LongDelimCheck( sc );
        if( sepCount == 0 ) {
          sc.SetState( SCE_LUA_OPERATOR );
        } else {
          nestLevel = 1;
          sc.SetState( SCE_LUA_LITERALSTRING );
          sc.Forward( sepCount );
        }
      } else if( sc.Match( '-', '-' ) ) {
        sc.SetState( SCE_LUA_COMMENTLINE );
        if( sc.Match( "--[" ) ) {
          sc.Forward( 2 );
          sepCount = LongDelimCheck( sc );
          if( sepCount > 0 ) {
            nestLevel = 1;
            sc.ChangeState( SCE_LUA_COMMENT );
            sc.Forward( sepCount );
          }
        } else
        { sc.Forward(); }
      } else if( sc.atLineStart && sc.Match( '$' ) ) {
        sc.SetState( SCE_LUA_PREPROCESSOR );
      } else if( setLuaOperator.Contains( sc.ch ) ) {
        sc.SetState( SCE_LUA_OPERATOR );
      }
    }
  }
  if( setWord.Contains( sc.chPrev ) || sc.chPrev == '.' ) {
    char s[100];
    sc.GetCurrent( s, sizeof( s ) );
    if( keywords.InList( s ) ) {
      sc.ChangeState( SCE_LUA_WORD );
    } else if( keywords2.InList( s ) ) {
      sc.ChangeState( SCE_LUA_WORD2 );
    } else if( keywords3.InList( s ) ) {
      sc.ChangeState( SCE_LUA_WORD3 );
    } else if( keywords4.InList( s ) ) {
      sc.ChangeState( SCE_LUA_WORD4 );
    } else if( keywords5.InList( s ) ) {
      sc.ChangeState( SCE_LUA_WORD5 );
    } else if( keywords6.InList( s ) ) {
      sc.ChangeState( SCE_LUA_WORD6 );
    } else if( keywords7.InList( s ) ) {
      sc.ChangeState( SCE_LUA_WORD7 );
    } else if( keywords8.InList( s ) ) {
      sc.ChangeState( SCE_LUA_WORD8 );
    }
  }
  sc.Complete();
}

static void FoldLuaDoc( unsigned int startPos, int length, int, WordList *[],
                        Accessor &styler ) {
  unsigned int lengthDoc = startPos + length;
  int visibleChars = 0;
  int lineCurrent = styler.GetLine( startPos );
  int levelPrev = styler.LevelAt( lineCurrent ) & SC_FOLDLEVELNUMBERMASK;
  int levelCurrent = levelPrev;
  char chNext = styler[startPos];
  bool foldCompact = styler.GetPropertyInt( "fold.compact", 1 ) != 0;
  int styleNext = styler.StyleAt( startPos );
  for( unsigned int i = startPos; i < lengthDoc; i++ ) {
    char ch = chNext;
    chNext = styler.SafeGetCharAt( i + 1 );
    int style = styleNext;
    styleNext = styler.StyleAt( i + 1 );
    bool atEOL = ( ch == '\r' && chNext != '\n' ) || ( ch == '\n' );
    if( style == SCE_LUA_WORD ) {
      if( ch == 'i' || ch == 'd' || ch == 'f' || ch == 'e' || ch == 'r' || ch == 'u' ) {
        char s[10] = "";
        for( unsigned int j = 0; j < 8; j++ ) {
          if( !iswordchar( styler[i + j] ) ) {
            break;
          }
          s[j] = styler[i + j];
          s[j + 1] = '\0';
        }
        if( ( strcmp( s, "if" ) == 0 ) || ( strcmp( s, "do" ) == 0 ) || ( strcmp( s, "function" ) == 0 ) || ( strcmp( s, "repeat" ) == 0 ) ) {
          levelCurrent++;
        }
        if( ( strcmp( s, "end" ) == 0 ) || ( strcmp( s, "elseif" ) == 0 ) || ( strcmp( s, "until" ) == 0 ) ) {
          levelCurrent--;
        }
      }
    } else if( style == SCE_LUA_OPERATOR ) {
      if( ch == '{' || ch == '(' ) {
        levelCurrent++;
      } else if( ch == '}' || ch == ')' ) {
        levelCurrent--;
      }
    } else if( style == SCE_LUA_LITERALSTRING || style == SCE_LUA_COMMENT ) {
      if( ch == '[' ) {
        levelCurrent++;
      } else if( ch == ']' ) {
        levelCurrent--;
      }
    }
    if( atEOL ) {
      int lev = levelPrev;
      if( visibleChars == 0 && foldCompact ) {
        lev |= SC_FOLDLEVELWHITEFLAG;
      }
      if( ( levelCurrent > levelPrev ) && ( visibleChars > 0 ) ) {
        lev |= SC_FOLDLEVELHEADERFLAG;
      }
      if( lev != styler.LevelAt( lineCurrent ) ) {
        styler.SetLevel( lineCurrent, lev );
      }
      lineCurrent++;
      levelPrev = levelCurrent;
      visibleChars = 0;
    }
    if( !isspacechar( ch ) ) {
      visibleChars++;
    }
  }
  int flagsNext = styler.LevelAt( lineCurrent ) & ~SC_FOLDLEVELNUMBERMASK;
  styler.SetLevel( lineCurrent, levelPrev | flagsNext );
}

static const char * const luaWordListDesc[] = {
  "Keywords",
  "Basic functions",
  "String, (table) & math functions",
  "(coroutines), I/O & system facilities",
  "user1",
  "user2",
  "user3",
  "user4",
  0
};

LexerModule lmLua( SCLEX_LUA, ColouriseLuaDoc, "lua", FoldLuaDoc, luaWordListDesc );
